//----------------------------------------------------------------------------------------------------------------------
// Copyright (c) 2012 James Whitworth
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the \"Software\"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//----------------------------------------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------------------------------------
// Wraps an array for member functions so the owner is set automatically.
//----------------------------------------------------------------------------------------------------------------------
class MemberArray
{
  owner = null
  contents = null

  constructor(_owner)
  {
    ::assert(typeof(_owner) == "instance")
    owner = _owner
    contents = ::array(0)
  }

  function _get(key)
  {
    return contents[key]
  }

  function _set(key, value)
  {
    assert(typeof(value) == "instance")
    value.owner = owner
    contents[key] = value
  }

  function _nexti(previous_index)
  {
    local count = contents.len()
    if (count > 0)
    {
      if (previous_index == null)
      {
        return 0
      }

      local next_index = previous_index + 1
      if (next_index < count)
      {
        return next_index
      }
    }
    return null
  }

  function len()
  {
    return contents.len()
  }

  function append(value)
  {
    value.owner = owner
    return contents.append(value)
  }
  
  function extend(other)
  {
    contents.extend(other.contents)
  }
}

//----------------------------------------------------------------------------------------------------------------------
// Describes a class for code generation
//----------------------------------------------------------------------------------------------------------------------
class Class extends CodeGenerator
{
  template_type = null

  public = null
  protected = null
  private = null

  constructor(_name, _flags = 0)
  {
    name = _name
    flags = _flags

    public = {
      functions = MemberArray(this)
      parameters = MemberArray(this)
    }
    protected = {
      functions = MemberArray(this)
      parameters = MemberArray(this)
    }
    private = {
      functions = MemberArray(this)
      parameters = MemberArray(this)
    }
  }

  function has_header_data()
  {
    return true
  }

  function has_inline_data()
  {
    if (is_templated())
    {
      return true
    }

    foreach (_function in public.functions)
    {
      if (_function.has_inline_data())
      {
        return true
      }
    }
    foreach (_function in protected.functions)
    {
      if (_function.has_inline_data())
      {
        return true
      }
    }
    foreach (_function in private.functions)
    {
      if (_function.has_inline_data())
      {
        return true
      }
    }

    return false
  }

  function has_source_data()
  {
    foreach (_function in public.functions)
    {
      if (_function.has_source_data())
      {
        return true
      }
    }
    foreach (parameter in public.parameters)
    {
      if (parameter.has_flags(CodeGenerator.kStatic))
      {
        return true
      }
    }
    foreach (_function in protected.functions)
    {
      if (_function.has_source_data())
      {
        return true
      }
    }
    foreach (parameter in protected.parameters)
    {
      if (parameter.has_flags(CodeGenerator.kStatic))
      {
        return true
      }
    }
    foreach (_function in private.functions)
    {
      if (_function.has_source_data())
      {
        return true
      }
    }
    foreach (parameter in private.parameters)
    {
      if (parameter.has_flags(CodeGenerator.kStatic))
      {
        return true
      }
    }

    return false
  }

  // Write the a declaration for this object.
  //
  function write_declaration(stream)
  {
    write_class_separator(stream)

    if (is_templated())
    {
      write_template(stream)
    }

    assert(typeof(name) == "string")
    assert(name.len() > 0)
    stream.write("class %s", name)
    stream.new_line()

    stream.write("{")
    stream.new_line()

    local section_names = [ "public", "protected", "private" ]
    foreach (section_name in section_names)
    {
      local section = get_section(section_name)

      assert(typeof(section) == "table")
      assert(typeof(section.functions) == "instance")
      assert(typeof(section.parameters) == "instance")

      local parameters = section.parameters
      local parameter_count = parameters.len()
      
      local functions = section.functions
      local function_count = functions.len()

      if ((parameter_count + function_count) > 0)
      {
        stream.write("%s:", section_name);
        stream.indent()
        stream.new_line()

        for (local i = 0; i != parameter_count; ++i)
        {
          parameters[i].write_declaration(stream)
          stream.write(";")
          stream.new_line()
        }

        if (parameter_count > 0 && function_count > 0)
        {
          stream.new_line()
        }

        for (local i = 0; i != function_count; ++i)
        {
          functions[i].write_declaration(stream)
        }

        stream.outdent()
      }
    }
    
    stream.write("};")
    stream.new_line()
  }

  // Write the a definition for this object.
  //
  function write_definition(stream, file_type)
  {
    assert(typeof(file_type) == "string")
    assert(file_type == "inline" || file_type == "source")
    local is_inline = (file_type == "inline")

    local parameters = MemberArray(this)
    local functions = MemberArray(this)

    local section_names = [ "public", "protected", "private" ]
    foreach (section_name in section_names)
    {
      local section = get_section(section_name)

      assert(typeof(section) == "table")
      assert(typeof(section.functions) == "instance")
      assert(typeof(section.parameters) == "instance")

      parameters.extend(section.parameters)
      functions.extend(section.functions)
    }

    local parameter_count = parameters.len()
    local function_count = functions.len()

    if ((parameter_count + function_count) > 0)
    {
      local requires_separator = false

      if (!is_inline)
      {
        for (local i = 0; i != parameter_count; ++i)
        {
          if (parameters[i].has_flags(CodeGenerator.kStatic))
          {
            if (!requires_separator)
            {
              write_class_separator(stream)
            }

            parameters[i].write_definition(stream, file_type)
            stream.write(";")
            stream.new_line()

            requires_separator = true
          }
        }
      }

      for (local i = 0; i != function_count; ++i)
      {
        if ((!is_inline && (functions[i].has_source_data() || is_templated())) ||
            (is_inline && (functions[i].has_inline_data() || is_templated())))
        {
          if (!requires_separator)
          {
            write_class_separator(stream)
          }

          if (requires_separator)
          {
            stream.new_line()
            stream.add_separator()
          }

          if (is_templated())
          {
            if (functions[i].is_specialisation())
            {
              stream.write("template<>")
              stream.new_line()
            }
            else
            {
              write_template(stream)
            }
          }
          functions[i].write_definition(stream, file_type)

          requires_separator = true
        }
      }

      return requires_separator
    }
  }

  // Write the required template declaration(s) for this object if they are needed
  //
  function write_template(stream)
  {
    if (is_templated())
    {
      assert(typeof(template_type) == "string")
      assert(template_type.len() > 0)

      stream.write("template<typename %s>", template_type)
      stream.new_line()
    }
  }

  // get a class section "public", "protected" or "private"
  function get_section(section)
  {
    assert(typeof(section) == "string")
    assert(section == "public" || section == "protected" || section == "private")
    assert(typeof(this[section]) == "table")
    return this[section]
  }
  
  function write_class_separator(stream)
  {
    assert(typeof(name) == "string")
    assert(name.len() > 0)

    stream.add_separator()
    stream.write("// %s", name)
    if (is_templated())
    {
      stream.write("<%s>", template_type)
    }
    stream.new_line()
    stream.add_separator()
  }
}